package org.jpush.core;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * A <code>ConnectionFactory</code> is used to store the connections.
 * And it will provide the features as below:
 * 1) Add a new connection
 * 2) Find an existing connection
 * 3) Disconnect an existing connection
 * 
 * @author Wang Lei
 *
 */
public class ConnectionFactory
{
	protected Server serverImpl;
	
	// Where the connection are stored
	private ConcurrentMap<String, LogicalConnection> connectionMap;
	
	public ConnectionFactory(Server serverImpl)
	{
		this.connectionMap = new ConcurrentHashMap<String, LogicalConnection>();
		this.serverImpl = serverImpl;
	}

	/**
	 * Store a new connection by the key, if exists, use the one stored
	 * 
	 * @param connection the new connection
	 * @return the connection stored
	 */
	public LogicalConnection addLogicalConnection(LogicalConnection connection)
	{
		String key = connection.getKey();
	    LogicalConnection ret = connection;

	    LogicalConnection value = connectionMap.get(key);
	    if (value != null)
	    {
	    	ret = value;
	    }
	    else
	    {
	    	connectionMap.put(key, connection);
	    }

	    ret.incrementUsage();
	    return ret;
	}

	/**
	 * Find a connection by the key (The key is the key of LogicConnection)
	 * @see <code>LogicalConnection#getKey()</code>
	 * @param key the key of LogicConnection
	 * @return The connection found, null if not found
	 */
	public LogicalConnection findLogicalConnection(String key)
	{
	    LogicalConnection value = null;
	    synchronized (connectionMap)
	    {
		    value = connectionMap.get(key);
	    }
	    
	    if (value != null)
	    {
	    	value.incrementUsage();
	    }
	    return value;
	}

	/**
	 * Close the connection without tell any other component
	 * @See <code>disconnect</code>
	 * 
	 * @param connection  The connection to be closed
	 */
	public void silentDisconnect(LogicalConnection connection)
	{
	    if (removeIfExisting(connection))
	    {
		    connection.physicalConnection.close(false);

		    ConnectionGC.getGC().addDisposableClient(connection);
	    }
	}

	/**
	 * Close the connection and notify the other component
	 * 
	 * @param connection  The connection to be closed
	 * @param waitForPendingPackets  Used to close Physical Connection
	 * @param blnReturnConnection  if true returnConnection will be called
	 */
	public void disconnect(LogicalConnection connection, 
			boolean waitForPendingPackets, boolean blnReturnConnection)
	{
	    if (removeIfExisting(connection))
	    {
	    	// Close Physical Connection first
		    connection.physicalConnection.close(waitForPendingPackets);

		    //Now it is set to disconnected.
		    serverImpl.getConnectionEventListener().onBeforeDisposeConnection(connection);
		    serverImpl.getBroadcastManager().removeConnection(connection.getKey());
		    serverImpl.getDispatcher().NotifyObserversClientOut(connection.getKey());

		    //Statistics
		    serverImpl.getServerStats().addToCumul(ServerStats.Measures.VisitorsHitsOut, 1);
		    serverImpl.getServerStats().addToCumul(ServerStats.Measures.VisitorsOnline, -1);
		    serverImpl.getServerStats().addToDuration(ServerStats.Measures.VisitorsDuration, connection.getVisitDuration());

		    if (blnReturnConnection)
		    {
	        	returnConnection(connection);;
		    }

		    //Send to garbage collector.
		    ConnectionGC.getGC().addDisposableClient(connection);
	    }
	}

	/**
	 * Remove the connection from the store if exists
	 * 
	 * @param connection  The connection to be removed
	 * @return true if the connection exists
	 */
	private boolean removeIfExisting(LogicalConnection connection)
	{
		boolean ret = false;
		if (connectionMap.remove(connection.getKey()) != null)
		{
			ret = true;
		}
		
		return ret;
	}

	/**
	 * Return how many connections are stored
	 * 
	 * @return the count of connections
	 */
	public int getConnectionCount()
	{
	    return connectionMap.size();
	}

	/**
	 * Return the connection
	 * @param connection The connection to be returned
	 */
	public void returnConnection(LogicalConnection connection)
	{
	    connection.decrementUsage();
	}
}
